import numpy as np
import random
from scipy.stats import cauchy
from deeptriage.Fine_Tuning import main as DE_Fitness
import copy
import logging
import time

'''
需要对DE算法进行大修，原先我以为一个个体是一个D×D的二维矩阵，但是在细究了基因的概念之后，发现并不是，
一个个体应该是一个一维矩阵，其长度为D×D，
大修成后者
'''
# logging = logging.setLogger('wlh')
logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# logger = logging.getLogger('master control system')     #
logger = logging.getLogger('天狼星系系内防御军团第四近卫舰队')     #


# N=100   # 种群大小
# G=100   # 最大迭代次数
# F = 0.5 # 突变算子
# Cr = 0.3    # 交叉概率
# D = 2   # 解空间的维度
# c_min = np.zeros((16, 16))
# print(c_min)
# c_max = np.ones((16, 16))
# print(c_max)

def captain(D=109, N=3, uF=0.5, uCr=0.5, beta=0.5, G=2):
	# 第二个维度为D-1是为了方便后来对角线位置插入0，作为正确分类的代价
	c_min = np.zeros((D*D))
	c_max = np.ones((D*D))
	'''
	自适应差分进化算法的入口
	传入的维度是不是应该比正常的少个1，毕竟分对的那个是0
	在设置解空间维度的时候，有个地方需要注意，因为需要空出索引0和1的位置分别放置空开发者和无法解析的开发者位置，
	因此，维度D要在原先的基础上+2
	:param N: population size 种群大小
	:param D: Dimension size 解空间维度
	:param uF: mutation factors 突变算子
	:param uCr: Cross probability 交叉概率
	:param G: Generation 代数
	:param c_min: 染色体的最小值
	:param c_max: 
	:return: 
	'''
	all_start_time = time.clock()
	fitness = [0 for _ in range(N)]  # 保留上轮迭代的fitness值，用来对比
	# 种群初始化
	C = np.zeros((G, N, D*D))
	for i in range(N):
		# 关于二维矩阵的乘法
		C[0][i] = c_min + np.random.random() * (c_max - c_min)  # 矩阵按位对应减
		logger.info('正在进行初始种群第{}号个体的fitness计算.'.format(i))
		fitness[i] = evaluate_fitness(C[0][i], D)
		logger.info('初始种群第{}号个体的fitness为{}.'.format(i, fitness[i]))
	print('差分进化种群初始化完成!耗时为：{}'.format(time.clock() - all_start_time))

	for g in range(G-1):

		S_Cr = []   # 保存所有成功的交叉概率,意思是, 成功交叉的样本中的交叉概率
		S_F = []        #同上
		# print('当前位于第{}代'.format(g))
		logger.info('当前位于第{}代'.format(g))
		for i in range(N):  # 循环种群内所有个体
			population_without_i = np.delete(C[g], i, 0)     # 删掉了当代种群中的第i个个体(当前旧个体)
			# 首先随机调整每个个体的突变算子值和交叉概率值
			Cr_i = np.random.normal(uCr, 0.1)
			# 很尴尬,我找了几个常用的包,random,np.random,scipy.stats, 它们内部都没有柯西分布的实现
			# 全都是标准柯西分布, 比较尴尬, 我得自己实现是么...

			# warning！warning！warning！
			F_i = np.random.standard_cauchy()       # 这里后续修改为柯西分布！！！！！也就是说这是个半成品。。。
			# 突变操作
			# 对当代种群的当前个体进行突变操作
			indices = random.sample(range(N - 1), 2)        # 随机寻找两个切片
			ci_mutation = C[g][i] + F_i * (population_without_i[indices[0]] - population_without_i[indices[1]])
			# 变异操作后,突变中间体的基因可能会超过上下限区间,为了保证基因取值在区间之内,对超过区间外的值赋值为边界值
			# 如果超过边界, 取边界值
			# 检查处理上边界
			ci_mutation = [ci_mutation[item_1] if ci_mutation[item_1] < c_max[item_1] else c_max[item_1] for item_1 in range(D*D)]
			# 检查并处理下边界
			ci_mutation = [ci_mutation[item_1] if ci_mutation[item_1] > c_min[item_1] else c_min[item_1] for item_1 in range(D*D)]
			# 交叉操作,对变异后的个体,根据交叉概率与适应度来确定最后的个体
			rand_i = np.random.randint(1, D)    # 保证每次必定有一个突变基因会遗传给下一代
			ci_new = [ci_mutation[item_1] if i == rand_i or np.random.random() < Cr_i else C[g][i][item_1] for item_1 in range(D*D)]
			# 适应度评价
			# if evaluate_fitness(C[g][i]) > evaluate_fitness(ci_new):
			new_fitness = evaluate_fitness(ci_new, D)
			if fitness[i] > new_fitness:    # 同上一代个体的适应度函数作对比
				C[g+1][i] = C[g][i]
				# 当前位置的fitness不变
			else:
				C[g+1][i] = ci_new  # 交叉成功,保留其相关算子
				S_Cr.append(Cr_i)
				S_F.append(F_i)
				# 当前位置的fitness改变
				logger.info('选择成功，从适应度{}提升至{}'.format(fitness[i], new_fitness))
				fitness[i] = new_fitness
		# 在每轮迭代结束,参数自适应调整
		uCr = (1-beta)*uCr + beta * (sum(S_Cr)/(len(S_Cr) if len(S_Cr) != 0 else 1))
		uF = (1-beta)*uF + beta * (sum(S_F)/(len(S_F) if len(S_F) != 0 else 1))
		logger.info("第{}代种群进化完毕.".format(g))
	# 寻找最后一代中适应度最佳的个体,为了节省时间,我应该需要记录每一轮所有个体的适应度值,方便selection操作时候进行比较
	# 也方便在最后一代中寻找最佳值
	print('整个实验最终耗时为：{}'.format(time.clock() - all_start_time))
	# 种群迭代完成
	# 保留最佳，并且注意输出
	optimal_index = np.argmax(fitness)
	logger.info('当前种群最佳个体的适应度值：{}'.format(np.max(fitness)))
	optimal_cost_matrix = C[-1][optimal_index]  # 取最后一代
	# 将最优代价矩阵保留
	with open('../data/optimal_cost_matrix.txt', 'w') as writer:
		for i in range(len(optimal_cost_matrix)):
			writer.write('{}/n'.format(optimal_cost_matrix[i]))



def evaluate_fitness(x, D):
	'''
	将len=D*D的一维矩阵转换成D*D的二维矩阵
	新增对角线为0，不需要为索引0和索引1位置填充
	:param x: 
	:return: 
	'''
	cost_matrix = [[0 for _ in range(D)]for _ in range(D)]
	sign = 0    # 计数，顺序输出x
	for i in range(D):
		for j in range(D):
			cost_matrix[i][j] = x[sign]
			if i == j:
				cost_matrix[i][j] = 0   # 指定位置置为0，即对角线位置
				# mis_vector.insert(i, 0)     # 在指定位置插入0，作为新增对角线
			sign += 1       # 相当于.next()

	top1, top5 = DE_Fitness(cost_matrix)
	return top1

if __name__ == '__main__':
	captain()